﻿namespace Stickam_Client
{
    public __gc class StickamRtmp
    {
        // Nested Types
        public: __delegate void __gc* ClosedEventHandler(String __gc* Reason);


        public: __delegate void __gc* ConnectedEventHandler();


        public: __delegate void __gc* DataArrivalEventHandler(Int32 __gc* Command, String __gc* Data);


        public: __delegate void __gc* HandshakeReplyEventHandler(String __gc* HandshakeData);


        // Events
        public: __event ClosedEventHandler __gc* Closed;
        public: __event ConnectedEventHandler __gc* Connected;
        public: __event DataArrivalEventHandler __gc* DataArrival;
        public: __event HandshakeReplyEventHandler __gc* HandshakeReply;

        // Methods
        public: StickamRtmp()
        {
            this->RtmpSocket = __gc new SocketConnector();
            this->EnableDataflow = true;
            this->SendEnabled = true;
        }

        public: void __gc* Close()
        {
            this->CloseConnection(S"Client closed the connection.");
        }

        private: void __gc* CloseConnection(String __gc* Reason)
        {
            this->RtmpSocket->Close();
            ClosedEventHandler __gc* closedEvent = this->ClosedEvent;
            if (closedEvent != 0)
            {
                closedEvent->Invoke(Reason);
            }
        }

        public: void __gc* Connect(String __gc* RoomIP)
        {
            this->FirstPacket = true;
            this->FirstPacketData = S"";
            this->LastData = S"";
            this->LastCommand = __gc new String __gc*[0x40];
            this->LastLength = __gc new String __gc*[0x40];
            this->RtmpSocket->Connect(RoomIP, MySettingsProperty::Settings->SocksIP, MySettingsProperty::Settings->SocksVersion, MySettingsProperty::Settings->SocksRTMP);
        }

        private: void __gc* ParseData(String __gc* Data)
        {
            Data = String::Concat(this->LastData, Data);
            this->LastData = S"";
            Int32 __gc* num = Strings::Asc(Strings::Mid(Data, 1, 1));
            Int32 __gc* num2 = this->HeaderLengths[(num >> 6)];
            Int32 __gc* index = num;
            if (index >= 0x80)
            {
                index -= 0x80;
            }
            if (index >= 0x40)
            {
                index -= 0x40;
            }
            if (Data->Length >= num2)
            {
                Int32 __gc* num4;
                Int32 __gc* num7;
                Int32 __gc* num5 = 0;
                Int32 __gc* num6 = 0;
                Int32 __gc* num8 = 0;
                Int32 __gc* num9 = 0;
                if (num2 >= 4)
                {
                    num9 = Globals::BytesToInt(Strings::Mid(Data, 2, 3));
                }
                if (num2 >= 8)
                {
                    num6 = Globals::BytesToInt(Strings::Mid(Data, 5, 3));
                    num5 = Strings::Asc(Strings::Mid(Data, 8, 1));
                }
                if (num2 == 12)
                {
                    num8 = Globals::BytesToInt(Strings::Mid(Data, 9, 4));
                }
                if (num2 < 8)
                {
                    num5 = Conversions::ToInteger(this->LastCommand[index]);
                    num6 = Conversions::ToInteger(this->LastLength[index]);
                }
                this->LastCommand[index] = Conversions::ToString(num5);
                this->LastLength[index] = Conversions::ToString(num6);
                if (num9 == 0xffffff)
                {
                    num7 = 0x85;
                    num4 = *static_cast<__box Int32*>(Math::Round(*static_cast<__box Double*>(((Conversion::Fix(*static_cast<__box Double*>((*static_cast<__box Double*>((num6 - 1)) / 128))) * 5) + 4))));
                }
                else
                {
                    num7 = 0x81;
                    num4 = *static_cast<__box Int32*>(Math::Round(Conversion::Fix(*static_cast<__box Double*>((*static_cast<__box Double*>((num6 - 1)) / 128)))));
                }
                num6 += num4;
                Int32 __gc* num10 = (num2 + num6);
                if (Data->Length >= num10)
                {
                    DataArrivalEventHandler __gc* dataArrivalEvent = this->DataArrivalEvent;
                    if (dataArrivalEvent != 0)
                    {
                        dataArrivalEvent->Invoke(num5, Globals::CleanSplitters(Strings::Mid(Data, (num2 + 1), num6), num7));
                    }
                    String __gc* data = Strings::Mid(Data, (num10 + 1));
                    if (data->Length > 0)
                    {
                        this->ParseData(data);
                    }
                }
                else
                {
                    this->LastData = Data;
                }
            }
            else
            {
                this->LastData = Data;
            }
        }

        public: void __gc* ResetParser()
        {
            this->LastData = S"";
        }

        private: void __gc* RtmpSocket_Closed()
        {
            this->CloseConnection(S"Server closed the connection.");
        }

        private: void __gc* RtmpSocket_Connected()
        {
            ConnectedEventHandler __gc* connectedEvent = this->ConnectedEvent;
            if (connectedEvent != 0)
            {
                connectedEvent->Invoke();
            }
            this->SendData(Globals::RtmpHandshake());
        }

        private: void __gc* RtmpSocket_ConnectFail()
        {
            this->CloseConnection(S"Could not establish a connection.");
        }

        private: void __gc* RtmpSocket_DataArrival(String __gc* Data)
        {
            if (this->EnableDataflow)
            {
                if (this->FirstPacket)
                {
                    this->FirstPacketData = String::Concat(this->FirstPacketData, Data);
                    if ((Strings::Asc(Strings::Mid(this->FirstPacketData, 1, 1)) == 3) & (this->FirstPacketData->Length == 0xc01))
                    {
                        this->FirstPacket = false;
                        HandshakeReplyEventHandler __gc* handshakeReplyEvent = this->HandshakeReplyEvent;
                        if (handshakeReplyEvent != 0)
                        {
                            handshakeReplyEvent->Invoke(Strings::Mid(this->FirstPacketData, 2, 0x600));
                        }
                    }
                    else if (this->FirstPacketData->Length >= 0xc01)
                    {
                        this->CloseConnection(S"Bad handshake reply from server. Connection closed.");
                    }
                }
                else if (Data->Length != 0)
                {
                    this->ParseData(Data);
                }
            }
        }

        public: void __gc* SendData(String __gc* Data)
        {
            if (this->SendEnabled)
            {
                this->RtmpSocket->SendData(Data);
            }
        }


        // Properties
        [DebuggerNonUserCode]
        private: __property virtual SocketConnector __gc* get_RtmpSocket()
        {
            return this->_RtmpSocket;
        }
        [MethodImpl(MethodImplOptions::Synchronized), DebuggerNonUserCode]
        private: __property virtual void __gc* set_RtmpSocket(SocketConnector __gc* WithEventsValue)
        {
            DataArrivalEventHandler __gc* handler = new DataArrivalEventHandler __gc*(this.RtmpSocket_DataArrival);
            ConnectFailEventHandler __gc* handler2 = new ConnectFailEventHandler __gc*(this.RtmpSocket_ConnectFail);
            ConnectedEventHandler __gc* handler3 = new ConnectedEventHandler __gc*(this.RtmpSocket_Connected);
            ClosedEventHandler __gc* handler4 = new ClosedEventHandler __gc*(this.RtmpSocket_Closed);
            if (this->_RtmpSocket != 0)
            {
                this->_RtmpSocket->DataArrival -= handler;
                this->_RtmpSocket->ConnectFail -= handler2;
                this->_RtmpSocket->Connected -= handler3;
                this->_RtmpSocket->Closed -= handler4;
            }
            this->_RtmpSocket = WithEventsValue;
            if (this->_RtmpSocket != 0)
            {
                this->_RtmpSocket->DataArrival += handler;
                this->_RtmpSocket->ConnectFail += handler2;
                this->_RtmpSocket->Connected += handler3;
                this->_RtmpSocket->Closed += handler4;
            }
        }


        // Fields
        [AccessedThroughProperty(S"RtmpSocket")]
        private: SocketConnector __gc* _RtmpSocket;
        private: ClosedEventHandler __gc* ClosedEvent;
        private: ConnectedEventHandler __gc* ConnectedEvent;
        private: DataArrivalEventHandler __gc* DataArrivalEvent;
        public: Boolean __gc* EnableDataflow;
        private: Boolean __gc* FirstPacket;
        private: String __gc* FirstPacketData;
        private: HandshakeReplyEventHandler __gc* HandshakeReplyEvent;
        private: Int32 __gc* HeaderLengths __gc [] = __gc new Int32 __gc*[4] {
            12, 8, 4, 1};
        private: String __gc* LastCommand __gc [] = __gc new String __gc*[0x40];
        private: String __gc* LastData;
        private: String __gc* LastLength __gc [] = __gc new String __gc*[0x40];
        private: Int32 __gc* PacketCountIn = 0;
        private: Int32 __gc* PacketCountOut = 0;
        public: Boolean __gc* SendEnabled;
    };
}

